Setup

First, install some new packages.

if(!require(tmap)){install.packages("tmap")}
if(!require(sf)){install.packages("sf")}
if(!require(maps)){install.packages("maps")}

Then, activate the ones we need.

library(tidyverse)
library(readxl)
library(tmap)        # Geographical plotting
library(gapminder)   # Country level data
library(maps)        # Geographical datasets
library(sf)          # Shape files (sf) are a common geographical format 
library(plotly)      # Dynamic graphs

Now, load the data (included in the packages). The World dataset contains geographical data.

data("World")
str(World)           # structure of World
Classes ‘sf’ and 'data.frame':  177 obs. of  16 variables:
 $ iso_a3      : Factor w/ 177 levels "AFG","AGO","ALB",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ name        : Factor w/ 177 levels "Afghanistan",..: 1 4 2 166 6 7 5 56 8 9 ...
 $ sovereignt  : Factor w/ 171 levels "Afghanistan",..: 1 4 2 159 6 7 5 52 8 9 ...
 $ continent   : Factor w/ 8 levels "Africa","Antarctica",..: 3 1 4 3 8 3 2 7 6 4 ...
 $ area        : Units: [km^2] num  652860 1246700 27400 71252 2736690 ...
 $ pop_est     : num  28400000 12799293 3639453 4798491 40913584 ...
 $ pop_est_dens: num  43.5 10.3 132.8 67.3 15 ...
 $ economy     : Factor w/ 7 levels "1. Developed region: G7",..: 7 7 6 6 5 6 6 6 2 2 ...
 $ income_grp  : Factor w/ 5 levels "1. High income: OECD",..: 5 3 4 2 3 4 2 2 1 1 ...
 $ gdp_cap_est : num  784 8618 5993 38408 14027 ...
 $ life_exp    : num  59.7 NA 77.3 NA 75.9 ...
 $ well_being  : num  3.8 NA 5.5 NA 6.5 4.3 NA NA 7.2 7.4 ...
 $ footprint   : num  0.79 NA 2.21 NA 3.14 2.23 NA NA 9.31 6.06 ...
 $ inequality  : num  0.427 NA 0.165 NA 0.164 ...
 $ HPI         : num  20.2 NA 36.8 NA 35.2 ...
 $ geometry    :sfc_MULTIPOLYGON of length 177; first list element: List of 1
  ..$ :List of 1
  .. ..$ : num [1:69, 1:2] 5310471 5408503 5470647 5477147 5541607 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
  ..- attr(*, "names")= chr [1:15] "iso_a3" "name" "sovereignt" "continent" ...
data("gapminder")
head(gapminder)

The world data is very rich: it has many attributes (population, economy, etc.). The one column we are interested in is geometry: it contains the data for the maps!

We see that a few points are missing for life expectancy, so let’s see if we can complete with the gapminder data (spoiler: not so much!).

This exercise (see below) of grouping two datasets is paramount when plotting geographical content because usually, the geometric properties come from one file and the items we wish to plot come from another file. Thus, the joining step is KEY.

Areas

Maps in which areas are coloured to show some numerical attribute are called choropleths.

The idea below is to merge the two sources to combine geographical forms and life expectancy. To do that, we need left_join(). But before, we need to create a common variable (i.e., a key) between the two. In World, the term “name” is not so great so let’s change it into “country” (just like in gapminder!). Then we can merge!

names(World)[2] <- "country"                    # Change the column name
gapminder %>% left_join(World, by = "country")  # Join the two datasets
nrow(gapminder)                                 # Number of instances

There are missing points. As a rough approximation, let’s just remove them and store the result in data.

data <- gapminder %>%                     # Join and save via the arrow operator <- 
  left_join(World, by = "country") %>%
  na.omit()                               # Remove rows with missing data
nrow(data)                                # Number of instances in dataset
[1] 1296

The loss is large because of missing points! We then use the tmap package to create a choropleth.

data %>% 
  filter(year == 2007) %>%    # Keep only one year (problems otherwise)
  sf::st_sf() %>%             # Transform data into shape file (sf) format
  tm_shape() +                # Tranforms the sf into a tmap object
  tm_polygons("lifeExp")      # Plot!

Some countries are missing => data problem because gapminder is incomplete. Let’s complete it via: https://www.gapminder.org/data/ This yields a much bigger dataset, called “gap2.RData”. Let’s see:

load("gap2.RData")
head(gap2)

It’s highly tidy! Let’s un-tidy it a bit to put it in the original gapminder format.

gap2 <- gap2 %>% pivot_wider(names_from = "indicator", values_from = "value")

Again, we resort to left_join to merge the two data sources.

data <- World %>% 
  left_join(gap2, by = "country") #%>%
  #na.omit() 

Next, we move towards interactive maps with the leaflet package. The advantage of leaflet lies in its multiple options that create dynamic plots. The example below is strongly inspired from the reference page: https://rstudio.github.io/leaflet/choropleths.html Thus, looking at the difference between the two codes helps understand how they work.

We are going two use two features:
- a customized color scale;
- labels that appear when the cursor is on the map.

For the scale, we define it manually, both for the colors and for the separation points (bins).
The labels are defined using html functions.

if(!require(leaflet)){install.packages("leaflet")}                  # Install package
library(leaflet)                                                    # Load package
datamap <- data %>% filter(year == 2011)                            # Keep only one year (recent)
palet <- colorBin("YlGnBu",                                         # Yellow-Green-Blue palette
                  domain = datamap %>% pull(lifeExp), # LifeExp     # Domain of labels: lifeExp
                  bins = c(50, 55, 60, 65, 70, 80, 90))             # Age categories

labels <- sprintf(                                                  # Below we define the labels
  "<strong>%s</strong><br/>%g Years",                               # Adding text to label
  datamap$country,                                                  # We show the country name...
  datamap$lifeExp                                                   # ... and the life expectancy
) %>% lapply(htmltools::HTML)                                       # Embedded all into html language

We can then move forward with the plot.

data %>% 
  filter(year == 2011) %>%                        # Keep the one year of data
  data.frame() %>%                                # Turn into dataframe (technical)
  sf::st_sf() %>%                                 # Format in sf
  st_transform("+init=epsg:4326") %>%             # Convert in particular coordinate reference 
  leaflet() %>%                                   # Call leaflet
  addPolygons(fillColor = ~palet(lifeExp),        # Create the map (colored polygons)
              weight = 2,                         # Width of separation line
              opacity = 1,                        # Opacity of separation line
              color = "white",                    # Color of separation line
              dashArray = "3",                    # Dash size of separation line
              fillOpacity = 0.7,                  # Opacity of polygon colors
              highlight = highlightOptions(       # 5 lines below control the cursor impact
                weight = 2,                       # Width of line
                color = "#0B3790",                # Color of line
                dashArray = "",                   # No dash
                fillOpacity = 0.7,                # Opacity
                bringToFront = TRUE),
              label = labels,                     # LABEL! Defined above!
              labelOptions = labelOptions(        # Label options below...
                style = list("font-weight" = "normal", padding = "3px 8px"),
                textsize = "15px",
                direction = "auto")
  ) %>%
  addLegend(pal = palet,                    # Legend: comes from palet colors defined above
            values = ~lifeExp,              # Values come from lifeExp variable
            opacity = 0.9,                  # Opacity of legend
            title = "Map Legend",           # Title of legend
            position = "bottomright")       # Position of legend

Better, but some African countries are still missing… Data problem!

It is possible to integrate leaflet into shiny: https://rstudio.github.io/leaflet/shiny.html You need renderLeaflet() in the server and leafletOutput() in the UI.

Points

Points are more easy to handle because they require less information (two coordinates at least plus other attributes like size or color). This is exactly the kind of data type that fits in the ggplot syntax! Below, we show how to combine ggplot with geographical data.

Country level data

While areas must be defined at the country level (complex!), points are simply characterized by two numbers: latitude & longitude. So a list of these two features suffices to plot points. For countries, it is possible to find data online, ex: https://developers.google.com/public-data/docs/canonical/countries_csv

The latitudes and longitudes correspond to the center of the countries.

Below, we create a new dataset with longitudes & latitutes combined with gapminder (updated version).

if(!require(readxl)){install.packages("readxl")}    # Install package to read excel files
library(readxl)                                     # Activate package
options(scipen=999)                                 # Remove scientific notation
country_LL <- read_excel("country_LL.xlsx")         # Read longitude/latitude data
data2 <- left_join(gap2, country_LL) %>%            # Merge two datasets
  na.omit() %>%                                     # Remove missing points
  mutate(latitude = as.numeric(latitude),           # 'Numerize' latitude
         longitude = as.numeric(longitude),         # 'Numerize' longitude
         population = round(population / 10^6,1))   # Scaling population into millions
data2 %>% head()

Be careful, the code below is computationally demanding (be patient). We use the viridis color palette. In ggplot, a small list of maps is available: world, France, Italy, New Zealand, US (and states).

if(!require(scales)){install.packages("scales")}
if(!require(viridis)){install.packages("viridis")} # Package for color gradient
library(viridis)                                    
library(scales) 
world <- map_data("world")
g <- ggplot(data = world) +                               # Data source
  geom_polygon(data = world, aes(x = long,                # Dark background of the world
                                 y = lat, 
                                 group = group)) +   
  geom_point(data = data2 %>% filter(year == 2018),       # Most recent data points
             aes(x = longitude,                           # Classical ggplot syntax!
                 y = latitude, 
                 size = population, 
                 color = lifeExp, 
                 label = country)) +                      # Label (for plotly)
  scale_color_viridis(direction = -1)                                   # Color scale
ggplotly(g)                                               # Plotly integration

City level data

You can find longitudes & latitudes by searching on Google. Here’s one example for France: https://simplemaps.com/data/fr-cities Worldwide data can be accessed here: https://simplemaps.com/data/world-cities

cities <- read_excel("worldcities.xlsx")

That’s too big. Let’s focus on Germany, Italy, Austria, Slovenia and Switzerland (arbitrarily).

cities_short <- cities %>% 
  filter(country %in% c("Germany", "Italy", "Switzerland", "Austria", "Slovenia"),
         population > 250000) %>%
  na.omit()
head(cities_short)
world <- map_data("world")
g <- ggplot(data = world) +                                     # Data source
  geom_polygon(data = world,                                    # Dark background of the world
               aes(x = long, y = lat, group = group),
               size = 0.5, color = "grey") +                    # Outer lines of polygons
  geom_point(data = cities_short,                               # The points
             aes(x = lng, 
                 y = lat, 
                 size = population, 
                 color = country, 
                 label = city)) +
  #scale_colour_gradient(low = "#FFFB00", high = "#FF1B00") +  # This line is for color = population
  scale_colour_manual(values = c("#FFEC00", "#66E234", "#1BCBF7", "#CC44F8", "#FC2361")) +
  coord_sf(xlim = c(5, 18), ylim = c(36, 57), expand = FALSE)             # Zoom on Europe
ggplotly(g)

Other maps

For points, it’s easy: just use the same coordinate system via longitude & latitude. It’s much more tricky for areas.

The key is to find shape files (one alternative format is GeoJSON, but it is not treated here).
Example: http://datapages.com/gis-map-publishing-program/gis-open-files/global-framework/ Be very careful: some maps are EXTREMELY precise (up to 1m precision). Thus, the files are heavy and the plotting takes a LOT of time. Below, we use 100m precision, which is largely enough.

The full description of all shape file items can be accessed on wikipedia:

  • .shp — shape format; the feature geometry itself
  • .shx — shape index format; a positional index of the feature geometry to allow seeking forwards and backwards quickly
  • .dbf — attribute format; columnar attributes for each shape, in dBase IV format
regions <- sf::st_read("regions-20140306-100m.shp")
Reading layer `regions-20140306-100m' from data source `/Users/coqueret/Documents/IT/COURS/2021/R4DS/S7_extensions/Geocomputing/regions-20140306-100m.shp' using driver `ESRI Shapefile'
Simple feature collection with 27 features and 10 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -61.80976 ymin: -21.38973 xmax: 55.83665 ymax: 51.08984
geographic CRS: WGS 84
str(regions)    # Structure of the variable region
Classes ‘sf’ and 'data.frame':  27 obs. of  11 variables:
 $ code_insee: chr  "42" "72" "83" "25" ...
 $ nom       : chr  "Alsace" "Aquitaine" "Auvergne" "Basse-Normandie" ...
 $ nom_cl    : chr  "Strasbourg" "Bordeaux" "Clermont-Ferrand" "Caen" ...
 $ insee_cl  : chr  "67482" "33063" "63113" "14118" ...
 $ nuts2     : chr  "FR42" "FR61" "FR72" "FR25" ...
 $ iso3166_2 : chr  NA NA NA NA ...
 $ wikipedia : chr  "fr:Alsace" "fr:Aquitaine" "fr:Auvergne" "fr:Basse-Normandie" ...
 $ nb_dep    : num  2 5 4 3 4 4 6 4 2 4 ...
 $ nb_comm   : num  904 2296 1310 1812 2046 ...
 $ surf_km2  : num  8328 41818 26172 17786 31752 ...
 $ geometry  :sfc_MULTIPOLYGON of length 27; first list element: List of 1
  ..$ :List of 1
  .. ..$ : num [1:760, 1:2] 7.43 7.43 7.42 7.42 7.4 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA
  ..- attr(*, "names")= chr [1:10] "code_insee" "nom" "nom_cl" "insee_cl" ...

That’s complicated. The only important column for now is the name of the region which is easy to recognize. Below, we plot a choropleth where the color is link to the area of the region (bigger = darker).

regions %>% 
  tm_shape() +
  tm_polygons("surf_km2")

That’s prety ugly. Let’s remove the regions outside the mainland.

regions %>%
  filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>% 
  tm_shape() +
  tm_polygons("surf_km2", border.col = "white") # With the border of polygons

Below, we change the color palette and plot the number of French “départements” inside each region.

regions %>%
  filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>% 
  tm_shape() +
  tm_fill("nb_dep", palette = "Spectral")  # Without the border

This is static: leaflet integration would be better.

Highcharts

The material below is inspired from the vignette https://cran.r-project.org/web/packages/highcharter/vignettes/charting-maps.html

The highcharter package creates good looking plots. Let’s install it (and activate it).

if(!require(highcharter)){install.packages("highcharter")}
library(highcharter)

The package has a large library of maps. The list is given here: https://code.highcharts.com/mapdata/

If you click on a link, you get the address of the map:

The last part is what you specify to get access to a map:

mapdata <- get_data_from_map(download_map_data("countries/fr/custom/fr-all-mainland"))
trying URL 'https://code.highcharts.com/mapdata/countries/fr/custom/fr-all-mainland.js'
Content type 'text/javascript' length 23676 bytes (23 KB)
==================================================
downloaded 23 KB
head(mapdata)

The information that will be plotted comes from this file. We need to add the value that we want to use for the plot. There are 21 regions in the variable map_data. This bypasses the merging of external data via the left_join() function.

mapdata <- mapdata %>% mutate(value = 1:nrow(mapdata)) # Completely random values
head(mapdata,20)

Below, we plot the regions of France and the color code is driven by the column “value” defined above.

hcmap("countries/fr/custom/fr-all-mainland",                      # Source for the map
      data = mapdata,                                             # Source for the color code
      value = "value",                                            # Column for color code
      joinBy = c("name"),                                         # Common name & label
      name = "Random Plot",                                       # Name of plot
      dataLabels = list(enabled = TRUE, format = "{point.name}"), # Allow labels
      borderColor = "#FAFAFA", borderWidth = 0.5,                 # Border info
      tooltip = list(valueDecimals = 1,                           # Label info
                     valuePrefix = "Value = ",                    # Before label
                     valueSuffix = " Units"))                     # After label
trying URL 'https://code.highcharts.com/mapdata/countries/fr/custom/fr-all-mainland.js'
Content type 'text/javascript' length 23676 bytes (23 KB)
==================================================
downloaded 23 KB

Fine grain local maps

Now let’s turn to smaller scale maps. The data comes from Kaggle, scrapped from tripadvisor. The useful columns: Longitude & Latitude, the MuseumName, the Rating and the LengthOfVisit.

With leaflet, it’s easy to obtain empty maps as long as you know the longitude & latitude of a particular location. Below: Lyon.

tripadvisor <- read_excel("tripadvisor_museum_US.xlsx") %>%    # Importing the data
  mutate(Longitude = as.numeric(Longitude),                    # Getting numbers back
         Latitude = as.numeric(Latitude),
         Rating = as.numeric(Rating),
         LengthOfVisit = as.factor(LengthOfVisit)) %>%
  filter(Latitude > 40, Latitude < 41, Longitude > (-74.5), Longitude < (-73.5)) # NY museums only
Problem with `mutate()` input `Longitude`.
ℹ NAs introduced by coercion
ℹ Input `Longitude` is `as.numeric(Longitude)`.NAs introduced by coercionProblem with `mutate()` input `Latitude`.
ℹ NAs introduced by coercion
ℹ Input `Latitude` is `as.numeric(Latitude)`.NAs introduced by coercion
tripadvisor

leaflet() %>%                                                  # An empty map: a good start!
  setView(lng = 4.85, lat = 45.75, zoom = 12) %>% 
  addTiles()
pal <- colorFactor(palette = "Spectral", domain = tripadvisor$LengthOfVisit)
tripadvisor %>%
  leaflet() %>%                                                  # A blank sheet...
  setView(lng = -73.9772, lat = 40.7808, zoom = 10) %>% 
  addTiles() %>%
  addCircles(lng = ~Longitude, lat = ~Latitude, 
             radius = ~Rating^4, label =  ~MuseumName,
             fillColor = ~pal(LengthOfVisit), fillOpacity = 0.9,  # Circle colors
             stroke = TRUE, color = "black", weight = 2) %>%      # Circle stroke
  addLegend(pal = pal,                    # Legend: comes from palet colors defined above
            values = ~LengthOfVisit,              # Values come from lifeExp variable
            opacity = 0.7,                  # Opacity of legend
            title = "Map Legend",           # Title of legend
            position = "bottomright")       # Position of legend

Below, you need to specify lon and lat in the source dataframe. Or you can use the hcaes() function

mapdata2 <- get_data_from_map(download_map_data("countries/us/custom/us-ny-congress-113"))
trying URL 'https://code.highcharts.com/mapdata/countries/us/custom/us-ny-congress-113.js'
Content type 'text/javascript' length 23596 bytes (23 KB)
==================================================
downloaded 23 KB
hcmap("countries/us/custom/us-ny-congress-113") %>%
  hc_mapNavigation(enabled = TRUE,
                   buttonOptions = list(
                     align = "right",
                     verticalAlign = "bottom")) %>%
  hc_add_series(data = tripadvisor %>% mutate(lon = Longitude, lat = Latitude, name = MuseumName),  # Add correct names
                type = "mappoint", name = "Museums") 
trying URL 'https://code.highcharts.com/mapdata/countries/us/custom/us-ny-congress-113.js'
Content type 'text/javascript' length 23596 bytes (23 KB)
==================================================
downloaded 23 KB

NA

Not as impressive as leaflet, but the map could probably be improved. NOTE: in the example above, the geographical background and the points are unrelated.

LS0tCnRpdGxlOiAiR2VvY29tcHV0aW5nIHdpdGggUiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCgojIFNldHVwCgpGaXJzdCwgaW5zdGFsbCBzb21lIG5ldyBwYWNrYWdlcy4KCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmlmKCFyZXF1aXJlKHRtYXApKXtpbnN0YWxsLnBhY2thZ2VzKCJ0bWFwIil9CmlmKCFyZXF1aXJlKHNmKSl7aW5zdGFsbC5wYWNrYWdlcygic2YiKX0KaWYoIXJlcXVpcmUobWFwcykpe2luc3RhbGwucGFja2FnZXMoIm1hcHMiKX0KYGBgCgoKVGhlbiwgYWN0aXZhdGUgdGhlIG9uZXMgd2UgbmVlZC4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHRtYXApICAgICAgICAjIEdlb2dyYXBoaWNhbCBwbG90dGluZwpsaWJyYXJ5KGdhcG1pbmRlcikgICAjIENvdW50cnkgbGV2ZWwgZGF0YQpsaWJyYXJ5KG1hcHMpICAgICAgICAjIEdlb2dyYXBoaWNhbCBkYXRhc2V0cwpsaWJyYXJ5KHNmKSAgICAgICAgICAjIFNoYXBlIGZpbGVzIChzZikgYXJlIGEgY29tbW9uIGdlb2dyYXBoaWNhbCBmb3JtYXQgCmxpYnJhcnkocGxvdGx5KSAgICAgICMgRHluYW1pYyBncmFwaHMKYGBgCgoKTm93LCBsb2FkIHRoZSBkYXRhIChpbmNsdWRlZCBpbiB0aGUgcGFja2FnZXMpLiBUaGUgV29ybGQgZGF0YXNldCBjb250YWlucyBnZW9ncmFwaGljYWwgZGF0YS4gCgpgYGB7cn0KZGF0YSgiV29ybGQiKQpzdHIoV29ybGQpICAgICAgICAgICAjIHN0cnVjdHVyZSBvZiBXb3JsZApkYXRhKCJnYXBtaW5kZXIiKQpoZWFkKGdhcG1pbmRlcikKYGBgCgpUaGUgd29ybGQgZGF0YSBpcyB2ZXJ5IHJpY2g6IGl0IGhhcyBtYW55IGF0dHJpYnV0ZXMgKHBvcHVsYXRpb24sIGVjb25vbXksIGV0Yy4pLiBUaGUgb25lIGNvbHVtbiB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBpcyAqKmdlb21ldHJ5Kio6IGl0IGNvbnRhaW5zIHRoZSBkYXRhIGZvciB0aGUgbWFwcyEKCldlIHNlZSB0aGF0IGEgZmV3IHBvaW50cyBhcmUgbWlzc2luZyBmb3IgbGlmZSBleHBlY3RhbmN5LCBzbyBsZXQncyBzZWUgaWYgd2UgY2FuIGNvbXBsZXRlIHdpdGggdGhlIGdhcG1pbmRlciBkYXRhIChzcG9pbGVyOiBub3Qgc28gbXVjaCEpLgoKKipUaGlzIGV4ZXJjaXNlIChzZWUgYmVsb3cpIG9mIGdyb3VwaW5nIHR3byBkYXRhc2V0cyBpcyBwYXJhbW91bnQgd2hlbiBwbG90dGluZyBnZW9ncmFwaGljYWwgY29udGVudCBiZWNhdXNlIHVzdWFsbHksIHRoZSBnZW9tZXRyaWMgcHJvcGVydGllcyBjb21lIGZyb20gb25lIGZpbGUgYW5kIHRoZSBpdGVtcyB3ZSB3aXNoIHRvIHBsb3QgY29tZSBmcm9tIGFub3RoZXIgZmlsZS4gVGh1cywgdGhlIGpvaW5pbmcgc3RlcCBpcyBLRVkuKioKCiMgQXJlYXMKCk1hcHMgaW4gd2hpY2ggYXJlYXMgYXJlIGNvbG91cmVkIHRvIHNob3cgc29tZSBudW1lcmljYWwgYXR0cmlidXRlIGFyZSBjYWxsZWQgKmNob3JvcGxldGhzKi4KClRoZSBpZGVhIGJlbG93IGlzIHRvIG1lcmdlIHRoZSB0d28gc291cmNlcyB0byBjb21iaW5lIGdlb2dyYXBoaWNhbCBmb3JtcyBhbmQgbGlmZSBleHBlY3RhbmN5LiBUbyBkbyB0aGF0LCB3ZSBuZWVkICoqbGVmdF9qb2luKiooKS4gQnV0IGJlZm9yZSwgd2UgbmVlZCB0byBjcmVhdGUgYSBjb21tb24gdmFyaWFibGUgKGkuZS4sIGEgKmtleSopIGJldHdlZW4gdGhlIHR3by4gSW4gV29ybGQsIHRoZSB0ZXJtICJuYW1lIiBpcyBub3Qgc28gZ3JlYXQgc28gbGV0J3MgY2hhbmdlIGl0IGludG8gImNvdW50cnkiIChqdXN0IGxpa2UgaW4gZ2FwbWluZGVyISkuIFRoZW4gd2UgY2FuIG1lcmdlIQoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpuYW1lcyhXb3JsZClbMl0gPC0gImNvdW50cnkiICAgICAgICAgICAgICAgICAgICAjIENoYW5nZSB0aGUgY29sdW1uIG5hbWUKZ2FwbWluZGVyICU+JSBsZWZ0X2pvaW4oV29ybGQsIGJ5ID0gImNvdW50cnkiKSAgIyBKb2luIHRoZSB0d28gZGF0YXNldHMKbnJvdyhnYXBtaW5kZXIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBOdW1iZXIgb2YgaW5zdGFuY2VzCmBgYAoKVGhlcmUgYXJlIG1pc3NpbmcgcG9pbnRzLiBBcyBhIHJvdWdoIGFwcHJveGltYXRpb24sIGxldCdzIGp1c3QgcmVtb3ZlIHRoZW0gYW5kIHN0b3JlIHRoZSByZXN1bHQgaW4gKipkYXRhKiouIAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpkYXRhIDwtIGdhcG1pbmRlciAlPiUgICAgICAgICAgICAgICAgICAgICAjIEpvaW4gYW5kIHNhdmUgdmlhIHRoZSBhcnJvdyBvcGVyYXRvciA8LSAKICBsZWZ0X2pvaW4oV29ybGQsIGJ5ID0gImNvdW50cnkiKSAlPiUKICBuYS5vbWl0KCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgZGF0YQpucm93KGRhdGEpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE51bWJlciBvZiBpbnN0YW5jZXMgaW4gZGF0YXNldApgYGAKClRoZSBsb3NzIGlzIGxhcmdlIGJlY2F1c2Ugb2YgbWlzc2luZyBwb2ludHMhIFdlIHRoZW4gdXNlIHRoZSAqdG1hcCogcGFja2FnZSB0byBjcmVhdGUgYSBjaG9yb3BsZXRoLgoKCmBgYHtyfQpkYXRhICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDA3KSAlPiUgICAgIyBLZWVwIG9ubHkgb25lIHllYXIgKHByb2JsZW1zIG90aGVyd2lzZSkKICBzZjo6c3Rfc2YoKSAlPiUgICAgICAgICAgICAgIyBUcmFuc2Zvcm0gZGF0YSBpbnRvIHNoYXBlIGZpbGUgKHNmKSBmb3JtYXQKICB0bV9zaGFwZSgpICsgICAgICAgICAgICAgICAgIyBUcmFuZm9ybXMgdGhlIHNmIGludG8gYSB0bWFwIG9iamVjdAogIHRtX3BvbHlnb25zKCJsaWZlRXhwIikgICAgICAjIFBsb3QhCmBgYAoKClNvbWUgY291bnRyaWVzIGFyZSBtaXNzaW5nID0+IGRhdGEgcHJvYmxlbSBiZWNhdXNlIGdhcG1pbmRlciBpcyBpbmNvbXBsZXRlLgpMZXQncyBjb21wbGV0ZSBpdCB2aWE6IGh0dHBzOi8vd3d3LmdhcG1pbmRlci5vcmcvZGF0YS8KVGhpcyB5aWVsZHMgYSBtdWNoIGJpZ2dlciBkYXRhc2V0LCBjYWxsZWQgImdhcDIuUkRhdGEiLiBMZXQncyBzZWU6CgpgYGB7cn0KbG9hZCgiZ2FwMi5SRGF0YSIpCmhlYWQoZ2FwMikKYGBgCgpJdCdzIGhpZ2hseSB0aWR5ISBMZXQncyB1bi10aWR5IGl0IGEgYml0IHRvIHB1dCBpdCBpbiB0aGUgb3JpZ2luYWwgKmdhcG1pbmRlciogZm9ybWF0LiAKCmBgYHtyfQpnYXAyIDwtIGdhcDIgJT4lIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAiaW5kaWNhdG9yIiwgdmFsdWVzX2Zyb20gPSAidmFsdWUiKQpgYGAKCkFnYWluLCB3ZSByZXNvcnQgdG8gbGVmdF9qb2luIHRvIG1lcmdlIHRoZSB0d28gZGF0YSBzb3VyY2VzLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpkYXRhIDwtIFdvcmxkICU+JSAKICBsZWZ0X2pvaW4oZ2FwMiwgYnkgPSAiY291bnRyeSIpICMlPiUKICAjbmEub21pdCgpIApgYGAKCgpOZXh0LCB3ZSBtb3ZlIHRvd2FyZHMgaW50ZXJhY3RpdmUgbWFwcyB3aXRoIHRoZSAqbGVhZmxldCogcGFja2FnZS4gVGhlIGFkdmFudGFnZSBvZiAqbGVhZmxldCogbGllcyBpbiBpdHMgbXVsdGlwbGUgb3B0aW9ucyB0aGF0IGNyZWF0ZSBkeW5hbWljIHBsb3RzLiBUaGUgZXhhbXBsZSBiZWxvdyBpcyBzdHJvbmdseSBpbnNwaXJlZCBmcm9tIHRoZSByZWZlcmVuY2UgcGFnZTogaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L2Nob3JvcGxldGhzLmh0bWwKVGh1cywgbG9va2luZyBhdCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gY29kZXMgaGVscHMgdW5kZXJzdGFuZCBob3cgdGhleSB3b3JrLiAKCldlIGFyZSBnb2luZyB0d28gdXNlIHR3byBmZWF0dXJlczogICAKLSBhIGN1c3RvbWl6ZWQgY29sb3Igc2NhbGU7ICAgIAotIGxhYmVscyB0aGF0IGFwcGVhciB3aGVuIHRoZSBjdXJzb3IgaXMgb24gdGhlIG1hcC4KCkZvciB0aGUgc2NhbGUsIHdlIGRlZmluZSBpdCBtYW51YWxseSwgYm90aCBmb3IgdGhlIGNvbG9ycyBhbmQgZm9yIHRoZSBzZXBhcmF0aW9uIHBvaW50cyAoYmlucykuICAgClRoZSBsYWJlbHMgYXJlIGRlZmluZWQgdXNpbmcgaHRtbCBmdW5jdGlvbnMuCgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQppZighcmVxdWlyZShsZWFmbGV0KSl7aW5zdGFsbC5wYWNrYWdlcygibGVhZmxldCIpfSAgICAgICAgICAgICAgICAgICMgSW5zdGFsbCBwYWNrYWdlCmxpYnJhcnkobGVhZmxldCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMb2FkIHBhY2thZ2UKZGF0YW1hcCA8LSBkYXRhICU+JSBmaWx0ZXIoeWVhciA9PSAyMDExKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEtlZXAgb25seSBvbmUgeWVhciAocmVjZW50KQpwYWxldCA8LSBjb2xvckJpbigiWWxHbkJ1IiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgWWVsbG93LUdyZWVuLUJsdWUgcGFsZXR0ZQogICAgICAgICAgICAgICAgICBkb21haW4gPSBkYXRhbWFwICU+JSBwdWxsKGxpZmVFeHApLCAjIExpZmVFeHAgICAgICMgRG9tYWluIG9mIGxhYmVsczogbGlmZUV4cAogICAgICAgICAgICAgICAgICBiaW5zID0gYyg1MCwgNTUsIDYwLCA2NSwgNzAsIDgwLCA5MCkpICAgICAgICAgICAgICMgQWdlIGNhdGVnb3JpZXMKCmxhYmVscyA8LSBzcHJpbnRmKCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCZWxvdyB3ZSBkZWZpbmUgdGhlIGxhYmVscwogICI8c3Ryb25nPiVzPC9zdHJvbmc+PGJyLz4lZyBZZWFycyIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQWRkaW5nIHRleHQgdG8gbGFiZWwKICBkYXRhbWFwJGNvdW50cnksICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFdlIHNob3cgdGhlIGNvdW50cnkgbmFtZS4uLgogIGRhdGFtYXAkbGlmZUV4cCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgLi4uIGFuZCB0aGUgbGlmZSBleHBlY3RhbmN5CikgJT4lIGxhcHBseShodG1sdG9vbHM6OkhUTUwpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBFbWJlZGRlZCBhbGwgaW50byBodG1sIGxhbmd1YWdlCmBgYAoKV2UgY2FuIHRoZW4gbW92ZSBmb3J3YXJkIHdpdGggdGhlIHBsb3QuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmRhdGEgJT4lIAogIGZpbHRlcih5ZWFyID09IDIwMTEpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCB0aGUgb25lIHllYXIgb2YgZGF0YQogIGRhdGEuZnJhbWUoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVHVybiBpbnRvIGRhdGFmcmFtZSAodGVjaG5pY2FsKQogIHNmOjpzdF9zZigpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRm9ybWF0IGluIHNmCiAgc3RfdHJhbnNmb3JtKCIraW5pdD1lcHNnOjQzMjYiKSAlPiUgICAgICAgICAgICAgIyBDb252ZXJ0IGluIHBhcnRpY3VsYXIgY29vcmRpbmF0ZSByZWZlcmVuY2UgCiAgbGVhZmxldCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDYWxsIGxlYWZsZXQKICBhZGRQb2x5Z29ucyhmaWxsQ29sb3IgPSB+cGFsZXQobGlmZUV4cCksICAgICAgICAjIENyZWF0ZSB0aGUgbWFwIChjb2xvcmVkIHBvbHlnb25zKQogICAgICAgICAgICAgIHdlaWdodCA9IDIsICAgICAgICAgICAgICAgICAgICAgICAgICMgV2lkdGggb2Ygc2VwYXJhdGlvbiBsaW5lCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsICAgICAgICAgICAgICAgICAgICAgICAgIyBPcGFjaXR5IG9mIHNlcGFyYXRpb24gbGluZQogICAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgICAgICAgICAgICAgICAgICAgICMgQ29sb3Igb2Ygc2VwYXJhdGlvbiBsaW5lCiAgICAgICAgICAgICAgZGFzaEFycmF5ID0gIjMiLCAgICAgICAgICAgICAgICAgICAgIyBEYXNoIHNpemUgb2Ygc2VwYXJhdGlvbiBsaW5lCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjcsICAgICAgICAgICAgICAgICAgIyBPcGFjaXR5IG9mIHBvbHlnb24gY29sb3JzCiAgICAgICAgICAgICAgaGlnaGxpZ2h0ID0gaGlnaGxpZ2h0T3B0aW9ucyggICAgICAgIyA1IGxpbmVzIGJlbG93IGNvbnRyb2wgdGhlIGN1cnNvciBpbXBhY3QKICAgICAgICAgICAgICAgIHdlaWdodCA9IDIsICAgICAgICAgICAgICAgICAgICAgICAjIFdpZHRoIG9mIGxpbmUKICAgICAgICAgICAgICAgIGNvbG9yID0gIiMwQjM3OTAiLCAgICAgICAgICAgICAgICAjIENvbG9yIG9mIGxpbmUKICAgICAgICAgICAgICAgIGRhc2hBcnJheSA9ICIiLCAgICAgICAgICAgICAgICAgICAjIE5vIGRhc2gKICAgICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC43LCAgICAgICAgICAgICAgICAjIE9wYWNpdHkKICAgICAgICAgICAgICAgIGJyaW5nVG9Gcm9udCA9IFRSVUUpLAogICAgICAgICAgICAgIGxhYmVsID0gbGFiZWxzLCAgICAgICAgICAgICAgICAgICAgICMgTEFCRUwhIERlZmluZWQgYWJvdmUhCiAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKCAgICAgICAgIyBMYWJlbCBvcHRpb25zIGJlbG93Li4uCiAgICAgICAgICAgICAgICBzdHlsZSA9IGxpc3QoImZvbnQtd2VpZ2h0IiA9ICJub3JtYWwiLCBwYWRkaW5nID0gIjNweCA4cHgiKSwKICAgICAgICAgICAgICAgIHRleHRzaXplID0gIjE1cHgiLAogICAgICAgICAgICAgICAgZGlyZWN0aW9uID0gImF1dG8iKQogICkgJT4lCiAgYWRkTGVnZW5kKHBhbCA9IHBhbGV0LCAgICAgICAgICAgICAgICAgICAgIyBMZWdlbmQ6IGNvbWVzIGZyb20gcGFsZXQgY29sb3JzIGRlZmluZWQgYWJvdmUKICAgICAgICAgICAgdmFsdWVzID0gfmxpZmVFeHAsICAgICAgICAgICAgICAjIFZhbHVlcyBjb21lIGZyb20gbGlmZUV4cCB2YXJpYWJsZQogICAgICAgICAgICBvcGFjaXR5ID0gMC45LCAgICAgICAgICAgICAgICAgICMgT3BhY2l0eSBvZiBsZWdlbmQKICAgICAgICAgICAgdGl0bGUgPSAiTWFwIExlZ2VuZCIsICAgICAgICAgICAjIFRpdGxlIG9mIGxlZ2VuZAogICAgICAgICAgICBwb3NpdGlvbiA9ICJib3R0b21yaWdodCIpICAgICAgICMgUG9zaXRpb24gb2YgbGVnZW5kCmBgYAoKCkJldHRlciwgYnV0IHNvbWUgQWZyaWNhbiBjb3VudHJpZXMgYXJlIHN0aWxsIG1pc3NpbmcuLi4gRGF0YSBwcm9ibGVtIQoKSXQgaXMgcG9zc2libGUgdG8gaW50ZWdyYXRlICpsZWFmbGV0KiBpbnRvICpzaGlueSo6IGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC9zaGlueS5odG1sCllvdSBuZWVkICoqcmVuZGVyTGVhZmxldCoqKCkgaW4gdGhlIHNlcnZlciBhbmQgKipsZWFmbGV0T3V0cHV0KiooKSBpbiB0aGUgVUkuCgoKIyBQb2ludHMKClBvaW50cyBhcmUgbW9yZSBlYXN5IHRvIGhhbmRsZSBiZWNhdXNlIHRoZXkgcmVxdWlyZSBsZXNzIGluZm9ybWF0aW9uICh0d28gY29vcmRpbmF0ZXMgYXQgbGVhc3QgcGx1cyBvdGhlciBhdHRyaWJ1dGVzIGxpa2Ugc2l6ZSBvciBjb2xvcikuIFRoaXMgaXMgZXhhY3RseSB0aGUga2luZCBvZiBkYXRhIHR5cGUgdGhhdCBmaXRzIGluIHRoZSAqKmdncGxvdCoqIHN5bnRheCEgQmVsb3csIHdlIHNob3cgaG93IHRvIGNvbWJpbmUgZ2dwbG90IHdpdGggZ2VvZ3JhcGhpY2FsIGRhdGEuCgojIyBDb3VudHJ5IGxldmVsIGRhdGEKCldoaWxlIGFyZWFzIG11c3QgYmUgZGVmaW5lZCBhdCB0aGUgY291bnRyeSBsZXZlbCAoY29tcGxleCEpLCBwb2ludHMgYXJlIHNpbXBseSBjaGFyYWN0ZXJpemVkIGJ5IHR3byBudW1iZXJzOiAqKmxhdGl0dWRlKiogJiAqKmxvbmdpdHVkZSoqLiBTbyBhIGxpc3Qgb2YgdGhlc2UgdHdvIGZlYXR1cmVzIHN1ZmZpY2VzIHRvIHBsb3QgcG9pbnRzLiBGb3IgY291bnRyaWVzLCBpdCBpcyBwb3NzaWJsZSB0byBmaW5kIGRhdGEgb25saW5lLCBleDogaHR0cHM6Ly9kZXZlbG9wZXJzLmdvb2dsZS5jb20vcHVibGljLWRhdGEvZG9jcy9jYW5vbmljYWwvY291bnRyaWVzX2NzdgoKVGhlIGxhdGl0dWRlcyBhbmQgbG9uZ2l0dWRlcyBjb3JyZXNwb25kIHRvIHRoZSBjZW50ZXIgb2YgdGhlIGNvdW50cmllcy4KCkJlbG93LCB3ZSBjcmVhdGUgYSBuZXcgZGF0YXNldCB3aXRoIGxvbmdpdHVkZXMgJiBsYXRpdHV0ZXMgY29tYmluZWQgd2l0aCAqZ2FwbWluZGVyKiAodXBkYXRlZCB2ZXJzaW9uKS4KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQppZighcmVxdWlyZShyZWFkeGwpKXtpbnN0YWxsLnBhY2thZ2VzKCJyZWFkeGwiKX0gICAgIyBJbnN0YWxsIHBhY2thZ2UgdG8gcmVhZCBleGNlbCBmaWxlcwpsaWJyYXJ5KHJlYWR4bCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBY3RpdmF0ZSBwYWNrYWdlCm9wdGlvbnMoc2NpcGVuPTk5OSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBzY2llbnRpZmljIG5vdGF0aW9uCmNvdW50cnlfTEwgPC0gcmVhZF9leGNlbCgiY291bnRyeV9MTC54bHN4IikgICAgICAgICAjIFJlYWQgbG9uZ2l0dWRlL2xhdGl0dWRlIGRhdGEKZGF0YTIgPC0gbGVmdF9qb2luKGdhcDIsIGNvdW50cnlfTEwpICU+JSAgICAgICAgICAgICMgTWVyZ2UgdHdvIGRhdGFzZXRzCiAgbmEub21pdCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBtaXNzaW5nIHBvaW50cwogIG11dGF0ZShsYXRpdHVkZSA9IGFzLm51bWVyaWMobGF0aXR1ZGUpLCAgICAgICAgICAgIyAnTnVtZXJpemUnIGxhdGl0dWRlCiAgICAgICAgIGxvbmdpdHVkZSA9IGFzLm51bWVyaWMobG9uZ2l0dWRlKSwgICAgICAgICAjICdOdW1lcml6ZScgbG9uZ2l0dWRlCiAgICAgICAgIHBvcHVsYXRpb24gPSByb3VuZChwb3B1bGF0aW9uIC8gMTBeNiwxKSkgICAjIFNjYWxpbmcgcG9wdWxhdGlvbiBpbnRvIG1pbGxpb25zCmRhdGEyICU+JSBoZWFkKCkKYGBgCgpCZSBjYXJlZnVsLCB0aGUgY29kZSBiZWxvdyBpcyBjb21wdXRhdGlvbmFsbHkgZGVtYW5kaW5nIChiZSBwYXRpZW50KS4gV2UgdXNlIHRoZSAqdmlyaWRpcyogY29sb3IgcGFsZXR0ZS4gCkluICpnZ3Bsb3QqLCBhIHNtYWxsIGxpc3Qgb2YgbWFwcyBpcyBhdmFpbGFibGU6IHdvcmxkLCBGcmFuY2UsIEl0YWx5LCBOZXcgWmVhbGFuZCwgVVMgKGFuZCBzdGF0ZXMpLiAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KaWYoIXJlcXVpcmUoc2NhbGVzKSl7aW5zdGFsbC5wYWNrYWdlcygic2NhbGVzIil9CmlmKCFyZXF1aXJlKHZpcmlkaXMpKXtpbnN0YWxsLnBhY2thZ2VzKCJ2aXJpZGlzIil9ICMgUGFja2FnZSBmb3IgY29sb3IgZ3JhZGllbnQKbGlicmFyeSh2aXJpZGlzKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApsaWJyYXJ5KHNjYWxlcykgCndvcmxkIDwtIG1hcF9kYXRhKCJ3b3JsZCIpCmcgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgc291cmNlCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSB3b3JsZCwgYWVzKHggPSBsb25nLCAgICAgICAgICAgICAgICAjIERhcmsgYmFja2dyb3VuZCBvZiB0aGUgd29ybGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxhdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gZ3JvdXApKSArICAgCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0YTIgJT4lIGZpbHRlcih5ZWFyID09IDIwMTgpLCAgICAgICAjIE1vc3QgcmVjZW50IGRhdGEgcG9pbnRzCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENsYXNzaWNhbCBnZ3Bsb3Qgc3ludGF4IQogICAgICAgICAgICAgICAgIHkgPSBsYXRpdHVkZSwgCiAgICAgICAgICAgICAgICAgc2l6ZSA9IHBvcHVsYXRpb24sIAogICAgICAgICAgICAgICAgIGNvbG9yID0gbGlmZUV4cCwgCiAgICAgICAgICAgICAgICAgbGFiZWwgPSBjb3VudHJ5KSkgKyAgICAgICAgICAgICAgICAgICAgICAjIExhYmVsIChmb3IgcGxvdGx5KQogIHNjYWxlX2NvbG9yX3ZpcmlkaXMoKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDb2xvciBzY2FsZQpnZ3Bsb3RseShnKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBQbG90bHkgaW50ZWdyYXRpb24KYGBgCgojIyBDaXR5IGxldmVsIGRhdGEKCllvdSBjYW4gZmluZCBsb25naXR1ZGVzICYgbGF0aXR1ZGVzIGJ5IHNlYXJjaGluZyBvbiBHb29nbGUuIEhlcmUncyBvbmUgZXhhbXBsZSBmb3IgRnJhbmNlOgpodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvZnItY2l0aWVzCldvcmxkd2lkZSBkYXRhIGNhbiBiZSBhY2Nlc3NlZCBoZXJlOiBodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvd29ybGQtY2l0aWVzCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmNpdGllcyA8LSByZWFkX2V4Y2VsKCJ3b3JsZGNpdGllcy54bHN4IikKYGBgCgpUaGF0J3MgdG9vIGJpZy4gTGV0J3MgZm9jdXMgb24gR2VybWFueSwgSXRhbHksIEF1c3RyaWEsIFNsb3ZlbmlhIGFuZCBTd2l0emVybGFuZCAoYXJiaXRyYXJpbHkpLgoKYGBge3J9CmNpdGllc19zaG9ydCA8LSBjaXRpZXMgJT4lIAogIGZpbHRlcihjb3VudHJ5ICVpbiUgYygiR2VybWFueSIsICJJdGFseSIsICJTd2l0emVybGFuZCIsICJBdXN0cmlhIiwgIlNsb3ZlbmlhIiksCiAgICAgICAgIHBvcHVsYXRpb24gPiAyNTAwMDApICU+JQogIG5hLm9taXQoKQpoZWFkKGNpdGllc19zaG9ydCkKYGBgCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CndvcmxkIDwtIG1hcF9kYXRhKCJ3b3JsZCIpCmcgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgc291cmNlCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSB3b3JsZCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhcmsgYmFja2dyb3VuZCBvZiB0aGUgd29ybGQKICAgICAgICAgICAgICAgYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwKICAgICAgICAgICAgICAgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZ3JleSIpICsgICAgICAgICAgICAgICAgICAgICMgT3V0ZXIgbGluZXMgb2YgcG9seWdvbnMKICBnZW9tX3BvaW50KGRhdGEgPSBjaXRpZXNfc2hvcnQsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVGhlIHBvaW50cwogICAgICAgICAgICAgYWVzKHggPSBsbmcsIAogICAgICAgICAgICAgICAgIHkgPSBsYXQsIAogICAgICAgICAgICAgICAgIHNpemUgPSBwb3B1bGF0aW9uLCAKICAgICAgICAgICAgICAgICBjb2xvciA9IGNvdW50cnksIAogICAgICAgICAgICAgICAgIGxhYmVsID0gY2l0eSkpICsKICAjc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdyA9ICIjRkZGQjAwIiwgaGlnaCA9ICIjRkYxQjAwIikgKyAgIyBUaGlzIGxpbmUgaXMgZm9yIGNvbG9yID0gcG9wdWxhdGlvbgogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiI0ZGRUMwMCIsICIjNjZFMjM0IiwgIiMxQkNCRjciLCAiI0NDNDRGOCIsICIjRkMyMzYxIikpICsKICBjb29yZF9zZih4bGltID0gYyg1LCAxOCksIHlsaW0gPSBjKDM2LCA1NyksIGV4cGFuZCA9IEZBTFNFKSAgICAgICAgICAgICAjIFpvb20gb24gRXVyb3BlCmdncGxvdGx5KGcpCmBgYAoKCiMgT3RoZXIgbWFwcwoKRm9yIHBvaW50cywgaXQncyBlYXN5OiBqdXN0IHVzZSB0aGUgc2FtZSBjb29yZGluYXRlIHN5c3RlbSB2aWEgbG9uZ2l0dWRlICYgbGF0aXR1ZGUuIEl0J3MgbXVjaCBtb3JlIHRyaWNreSBmb3IgYXJlYXMuCgpUaGUga2V5IGlzIHRvIGZpbmQgKipzaGFwZSBmaWxlcyoqIChvbmUgYWx0ZXJuYXRpdmUgZm9ybWF0IGlzIEdlb0pTT04sIGJ1dCBpdCBpcyBub3QgdHJlYXRlZCBoZXJlKS4gIApFeGFtcGxlOiBodHRwOi8vZGF0YXBhZ2VzLmNvbS9naXMtbWFwLXB1Ymxpc2hpbmctcHJvZ3JhbS9naXMtb3Blbi1maWxlcy9nbG9iYWwtZnJhbWV3b3JrLyAKQmUgdmVyeSBjYXJlZnVsOiBzb21lIG1hcHMgYXJlICpFWFRSRU1FTFkqIHByZWNpc2UgKHVwIHRvIDFtIHByZWNpc2lvbikuIFRodXMsIHRoZSBmaWxlcyBhcmUgaGVhdnkgYW5kIHRoZSBwbG90dGluZyB0YWtlcyBhICpMT1QqIG9mIHRpbWUuIEJlbG93LCB3ZSB1c2UgMTAwbSBwcmVjaXNpb24sIHdoaWNoIGlzIGxhcmdlbHkgZW5vdWdoLgoKVGhlIGZ1bGwgZGVzY3JpcHRpb24gb2YgYWxsIHNoYXBlIGZpbGUgaXRlbXMgY2FuIGJlIGFjY2Vzc2VkIG9uIHdpa2lwZWRpYToKCiogLnNocCDigJQgc2hhcGUgZm9ybWF0OyB0aGUgZmVhdHVyZSBnZW9tZXRyeSBpdHNlbGYgICAKKiAuc2h4IOKAlCBzaGFwZSBpbmRleCBmb3JtYXQ7IGEgcG9zaXRpb25hbCBpbmRleCBvZiB0aGUgZmVhdHVyZSBnZW9tZXRyeSB0byBhbGxvdyBzZWVraW5nIGZvcndhcmRzIGFuZCBiYWNrd2FyZHMgcXVpY2tseSAgIAoqIC5kYmYg4oCUIGF0dHJpYnV0ZSBmb3JtYXQ7IGNvbHVtbmFyIGF0dHJpYnV0ZXMgZm9yIGVhY2ggc2hhcGUsIGluIGRCYXNlIElWIGZvcm1hdAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpyZWdpb25zIDwtIHNmOjpzdF9yZWFkKCJyZWdpb25zLTIwMTQwMzA2LTEwMG0uc2hwIikKc3RyKHJlZ2lvbnMpICAgICMgU3RydWN0dXJlIG9mIHRoZSB2YXJpYWJsZSByZWdpb24KYGBgCgpUaGF0J3MgY29tcGxpY2F0ZWQuIFRoZSBvbmx5IGltcG9ydGFudCBjb2x1bW4gZm9yIG5vdyBpcyB0aGUgKm5hbWUqIG9mIHRoZSByZWdpb24gd2hpY2ggaXMgZWFzeSB0byByZWNvZ25pemUuIEJlbG93LCB3ZSBwbG90IGEgY2hvcm9wbGV0aCB3aGVyZSB0aGUgY29sb3IgaXMgbGluayB0byB0aGUgYXJlYSBvZiB0aGUgcmVnaW9uIChiaWdnZXIgPSBkYXJrZXIpLgoKYGBge3J9CnJlZ2lvbnMgJT4lIAogIHRtX3NoYXBlKCkgKwogIHRtX3BvbHlnb25zKCJzdXJmX2ttMiIpCmBgYAoKVGhhdCdzIHByZXR5IHVnbHkuIExldCdzIHJlbW92ZSB0aGUgcmVnaW9ucyBvdXRzaWRlIHRoZSBtYWlubGFuZC4gCgpgYGB7cn0KcmVnaW9ucyAlPiUKICBmaWx0ZXIoISAobm9tICVpbiUgYygiR3V5YW5lIiwgIk1heW90dGUiLCAiTWFydGluaXF1ZSIsICJHdWFkZWxvdXBlIiwgIkxhIFLDqXVuaW9uIikpKSAlPiUgCiAgdG1fc2hhcGUoKSArCiAgdG1fcG9seWdvbnMoInN1cmZfa20yIiwgYm9yZGVyLmNvbCA9ICJ3aGl0ZSIpICMgV2l0aCB0aGUgYm9yZGVyIG9mIHBvbHlnb25zCmBgYAoKQmVsb3csIHdlIGNoYW5nZSB0aGUgY29sb3IgcGFsZXR0ZSBhbmQgcGxvdCB0aGUgbnVtYmVyIG9mIEZyZW5jaCAiZMOpcGFydGVtZW50cyIgaW5zaWRlIGVhY2ggcmVnaW9uLgoKYGBge3J9CnJlZ2lvbnMgJT4lCiAgZmlsdGVyKCEgKG5vbSAlaW4lIGMoIkd1eWFuZSIsICJNYXlvdHRlIiwgIk1hcnRpbmlxdWUiLCAiR3VhZGVsb3VwZSIsICJMYSBSw6l1bmlvbiIpKSkgJT4lIAogIHRtX3NoYXBlKCkgKwogIHRtX2ZpbGwoIm5iX2RlcCIsIHBhbGV0dGUgPSAiU3BlY3RyYWwiKSAgIyBXaXRob3V0IHRoZSBib3JkZXIKYGBgCgpUaGlzIGlzIHN0YXRpYzogKmxlYWZsZXQqIGludGVncmF0aW9uIHdvdWxkIGJlIGJldHRlci4KCiMgSGlnaGNoYXJ0cwoKVGhlIG1hdGVyaWFsIGJlbG93IGlzIGluc3BpcmVkIGZyb20gdGhlIHZpZ25ldHRlIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9oaWdoY2hhcnRlci92aWduZXR0ZXMvY2hhcnRpbmctbWFwcy5odG1sCgpUaGUgKmhpZ2hjaGFydGVyKiBwYWNrYWdlIGNyZWF0ZXMgZ29vZCBsb29raW5nIHBsb3RzLiBMZXQncyBpbnN0YWxsIGl0IChhbmQgYWN0aXZhdGUgaXQpLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IFRSVUV9CmlmKCFyZXF1aXJlKGhpZ2hjaGFydGVyKSl7aW5zdGFsbC5wYWNrYWdlcygiaGlnaGNoYXJ0ZXIiKX0KbGlicmFyeShoaWdoY2hhcnRlcikKYGBgCgpUaGUgcGFja2FnZSBoYXMgYSBsYXJnZSBsaWJyYXJ5IG9mIG1hcHMuIFRoZSBsaXN0IGlzIGdpdmVuIGhlcmU6Cmh0dHBzOi8vY29kZS5oaWdoY2hhcnRzLmNvbS9tYXBkYXRhLwoKSWYgeW91IGNsaWNrIG9uIGEgbGluaywgeW91IGdldCB0aGUgYWRkcmVzcyBvZiB0aGUgbWFwOgoKIVtdKGhpZ2hfbGlzdC5wbmcpCgpUaGUgbGFzdCBwYXJ0IGlzIHdoYXQgeW91IHNwZWNpZnkgdG8gZ2V0IGFjY2VzcyB0byBhIG1hcDoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbWFwZGF0YSA8LSBnZXRfZGF0YV9mcm9tX21hcChkb3dubG9hZF9tYXBfZGF0YSgiY291bnRyaWVzL2ZyL2N1c3RvbS9mci1hbGwtbWFpbmxhbmQiKSkKaGVhZChtYXBkYXRhKQpgYGAKClRoZSBpbmZvcm1hdGlvbiB0aGF0IHdpbGwgYmUgcGxvdHRlZCBjb21lcyBmcm9tIHRoaXMgZmlsZS4gV2UgbmVlZCB0byBhZGQgdGhlIHZhbHVlIHRoYXQgd2Ugd2FudCB0byB1c2UgZm9yIHRoZSBwbG90LiBUaGVyZSBhcmUgMjEgcmVnaW9ucyBpbiB0aGUgdmFyaWFibGUgKm1hcF9kYXRhKi4gVGhpcyBieXBhc3NlcyB0aGUgbWVyZ2luZyBvZiBleHRlcm5hbCBkYXRhIHZpYSB0aGUgKipsZWZ0X2pvaW4qKigpIGZ1bmN0aW9uLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQptYXBkYXRhIDwtIG1hcGRhdGEgJT4lIG11dGF0ZSh2YWx1ZSA9IDE6bnJvdyhtYXBkYXRhKSkgIyBDb21wbGV0ZWx5IHJhbmRvbSB2YWx1ZXMKaGVhZChtYXBkYXRhLDIwKQpgYGAKCkJlbG93LCB3ZSBwbG90IHRoZSByZWdpb25zIG9mIEZyYW5jZSBhbmQgdGhlIGNvbG9yIGNvZGUgaXMgZHJpdmVuIGJ5IHRoZSBjb2x1bW4gInZhbHVlIiBkZWZpbmVkIGFib3ZlLiAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KaGNtYXAoImNvdW50cmllcy9mci9jdXN0b20vZnItYWxsLW1haW5sYW5kIiwgICAgICAgICAgICAgICAgICAgICAgIyBTb3VyY2UgZm9yIHRoZSBtYXAKICAgICAgZGF0YSA9IG1hcGRhdGEsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTb3VyY2UgZm9yIHRoZSBjb2xvciBjb2RlCiAgICAgIHZhbHVlID0gInZhbHVlIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ29sdW1uIGZvciBjb2xvciBjb2RlCiAgICAgIGpvaW5CeSA9IGMoIm5hbWUiKSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ29tbW9uIG5hbWUgJiBsYWJlbAogICAgICBuYW1lID0gIlJhbmRvbSBQbG90IiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE5hbWUgb2YgcGxvdAogICAgICBkYXRhTGFiZWxzID0gbGlzdChlbmFibGVkID0gVFJVRSwgZm9ybWF0ID0gIntwb2ludC5uYW1lfSIpLCAjIEFsbG93IGxhYmVscwogICAgICBib3JkZXJDb2xvciA9ICIjRkFGQUZBIiwgYm9yZGVyV2lkdGggPSAwLjUsICAgICAgICAgICAgICAgICAjIEJvcmRlciBpbmZvCiAgICAgIHRvb2x0aXAgPSBsaXN0KHZhbHVlRGVjaW1hbHMgPSAxLCAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTGFiZWwgaW5mbwogICAgICAgICAgICAgICAgICAgICB2YWx1ZVByZWZpeCA9ICJWYWx1ZSA9ICIsICAgICAgICAgICAgICAgICAgICAjIEJlZm9yZSBsYWJlbAogICAgICAgICAgICAgICAgICAgICB2YWx1ZVN1ZmZpeCA9ICIgVW5pdHMiKSkgICAgICAgICAgICAgICAgICAgICAjIEFmdGVyIGxhYmVsCmBgYAoKCiMgRmluZSBncmFpbiBsb2NhbCBtYXBzCgpOb3cgbGV0J3MgdHVybiB0byBzbWFsbGVyIHNjYWxlIG1hcHMuIFRoZSBkYXRhIGNvbWVzIGZyb20gS2FnZ2xlLCBzY3JhcHBlZCBmcm9tIHRyaXBhZHZpc29yLiBUaGUgdXNlZnVsIGNvbHVtbnM6ICpMb25naXR1ZGUqICYgKkxhdGl0dWRlKiwgdGhlICpNdXNldW1OYW1lKiwgdGhlICpSYXRpbmcqIGFuZCB0aGUgKkxlbmd0aE9mVmlzaXQqLiAKCldpdGggbGVhZmxldCwgaXQncyBlYXN5IHRvIG9idGFpbiBlbXB0eSBtYXBzIGFzIGxvbmcgYXMgeW91IGtub3cgdGhlIGxvbmdpdHVkZSAmIGxhdGl0dWRlIG9mIGEgcGFydGljdWxhciBsb2NhdGlvbi4gQmVsb3c6ICoqTHlvbioqLgoKYGBge3J9CnRyaXBhZHZpc29yIDwtIHJlYWRfZXhjZWwoInRyaXBhZHZpc29yX211c2V1bV9VUy54bHN4IikgJT4lICAgICMgSW1wb3J0aW5nIHRoZSBkYXRhCiAgbXV0YXRlKExvbmdpdHVkZSA9IGFzLm51bWVyaWMoTG9uZ2l0dWRlKSwgICAgICAgICAgICAgICAgICAgICMgR2V0dGluZyBudW1iZXJzIGJhY2sKICAgICAgICAgTGF0aXR1ZGUgPSBhcy5udW1lcmljKExhdGl0dWRlKSwKICAgICAgICAgUmF0aW5nID0gYXMubnVtZXJpYyhSYXRpbmcpLAogICAgICAgICBMZW5ndGhPZlZpc2l0ID0gYXMuZmFjdG9yKExlbmd0aE9mVmlzaXQpKSAlPiUKICBmaWx0ZXIoTGF0aXR1ZGUgPiA0MCwgTGF0aXR1ZGUgPCA0MSwgTG9uZ2l0dWRlID4gKC03NC41KSwgTG9uZ2l0dWRlIDwgKC03My41KSkgIyBOWSBtdXNldW1zIG9ubHkKdHJpcGFkdmlzb3IKCmxlYWZsZXQoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQW4gZW1wdHkgbWFwOiBhIGdvb2Qgc3RhcnQhCiAgc2V0VmlldyhsbmcgPSA0Ljg1LCBsYXQgPSA0NS43NSwgem9vbSA9IDEyKSAlPiUgCiAgYWRkVGlsZXMoKQpgYGAKCmBgYHtyfQpwYWwgPC0gY29sb3JGYWN0b3IocGFsZXR0ZSA9ICJTcGVjdHJhbCIsIGRvbWFpbiA9IHRyaXBhZHZpc29yJExlbmd0aE9mVmlzaXQpCnRyaXBhZHZpc29yICU+JQogIGxlYWZsZXQoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQSBibGFuayBzaGVldC4uLgogIHNldFZpZXcobG5nID0gLTczLjk3NzIsIGxhdCA9IDQwLjc4MDgsIHpvb20gPSAxMCkgJT4lIAogIGFkZFRpbGVzKCkgJT4lCiAgYWRkQ2lyY2xlcyhsbmcgPSB+TG9uZ2l0dWRlLCBsYXQgPSB+TGF0aXR1ZGUsIAogICAgICAgICAgICAgcmFkaXVzID0gflJhdGluZ140LCBsYWJlbCA9ICB+TXVzZXVtTmFtZSwKICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWwoTGVuZ3RoT2ZWaXNpdCksIGZpbGxPcGFjaXR5ID0gMC45LCAgIyBDaXJjbGUgY29sb3JzCiAgICAgICAgICAgICBzdHJva2UgPSBUUlVFLCBjb2xvciA9ICJibGFjayIsIHdlaWdodCA9IDIpICU+JSAgICAgICMgQ2lyY2xlIHN0cm9rZQogIGFkZExlZ2VuZChwYWwgPSBwYWwsICAgICAgICAgICAgICAgICAgICAjIExlZ2VuZDogY29tZXMgZnJvbSBwYWxldCBjb2xvcnMgZGVmaW5lZCBhYm92ZQogICAgICAgICAgICB2YWx1ZXMgPSB+TGVuZ3RoT2ZWaXNpdCwgICAgICAgICAgICAgICMgVmFsdWVzIGNvbWUgZnJvbSBsaWZlRXhwIHZhcmlhYmxlCiAgICAgICAgICAgIG9wYWNpdHkgPSAwLjcsICAgICAgICAgICAgICAgICAgIyBPcGFjaXR5IG9mIGxlZ2VuZAogICAgICAgICAgICB0aXRsZSA9ICJNYXAgTGVnZW5kIiwgICAgICAgICAgICMgVGl0bGUgb2YgbGVnZW5kCiAgICAgICAgICAgIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0IikgICAgICAgIyBQb3NpdGlvbiBvZiBsZWdlbmQKYGBgCgpCZWxvdywgeW91IG5lZWQgdG8gc3BlY2lmeSAqKmxvbioqIGFuZCAqKmxhdCoqIGluIHRoZSBzb3VyY2UgZGF0YWZyYW1lLiBPciB5b3UgY2FuIHVzZSB0aGUgKmhjYWVzKigpIGZ1bmN0aW9uCgpgYGB7cn0KbWFwZGF0YTIgPC0gZ2V0X2RhdGFfZnJvbV9tYXAoZG93bmxvYWRfbWFwX2RhdGEoImNvdW50cmllcy91cy9jdXN0b20vdXMtbnktY29uZ3Jlc3MtMTEzIikpCmhjbWFwKCJjb3VudHJpZXMvdXMvY3VzdG9tL3VzLW55LWNvbmdyZXNzLTExMyIpICU+JQogIGhjX21hcE5hdmlnYXRpb24oZW5hYmxlZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICBidXR0b25PcHRpb25zID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgYWxpZ24gPSAicmlnaHQiLAogICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbEFsaWduID0gImJvdHRvbSIpKSAlPiUKICBoY19hZGRfc2VyaWVzKGRhdGEgPSB0cmlwYWR2aXNvciAlPiUgbXV0YXRlKGxvbiA9IExvbmdpdHVkZSwgbGF0ID0gTGF0aXR1ZGUsIG5hbWUgPSBNdXNldW1OYW1lKSwgICMgQWRkIGNvcnJlY3QgbmFtZXMKICAgICAgICAgICAgICAgIHR5cGUgPSAibWFwcG9pbnQiLCBuYW1lID0gIk11c2V1bXMiKSAKICAKYGBgCgpOb3QgYXMgaW1wcmVzc2l2ZSBhcyBsZWFmbGV0LCBidXQgdGhlIG1hcCBjb3VsZCBwcm9iYWJseSBiZSBpbXByb3ZlZC4gCioqTk9URSoqOiBpbiB0aGUgZXhhbXBsZSBhYm92ZSwgdGhlIGdlb2dyYXBoaWNhbCBiYWNrZ3JvdW5kIGFuZCB0aGUgcG9pbnRzIGFyZSB1bnJlbGF0ZWQuCgojIFJlc291cmNlcwoKQmVsb3csIGEgbGluayBvZiBncmVhdCBwYWdlcyAmIHR1dG9yaWFsczoKIAoqKkdlb2NvbXB1dGF0aW9uIHdpdGggUioqIChvbmxpbmUgYm9vayk6IGh0dHBzOi8vZ2VvY29tcHIucm9iaW5sb3ZlbGFjZS5uZXQgICAgCioqZ2dwbG90KiohOiBodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvbWFwX2RhdGEuaHRtbFB1cmUgICAgIAoqKnNmKiogKHNoYXBlIGZpbGVzKTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NmL3ZpZ25ldHRlcy9zZjEuaHRtbCAgIAoqKnNmICYgUioqOiBodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vc2YvYXJ0aWNsZXMvc2YxLmh0bWwgICAKKipzZiAmIGdncGxvdCoqOiBodHRwczovL3d3dy5yLXNwYXRpYWwub3JnL3IvMjAxOC8xMC8yNS9nZ3Bsb3QyLXNmLTIuaHRtbCAgIAoqKmxpc3Qgb2Ygc2YqKjogaHR0cDovL2RhdGFwYWdlcy5jb20vZ2lzLW1hcC1wdWJsaXNoaW5nLXByb2dyYW0vZ2lzLW9wZW4tZmlsZXMvZ2xvYmFsLWZyYW1ld29yay9nbG9iYWwtaGVhdC1mbG93LWRhdGFiYXNlL3NoYXBlZmlsZXMtbGlzdCAgCioqdG1hcCoqOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdG1hcC92aWduZXR0ZXMvdG1hcC1nZXRzdGFydGVkLmh0bWwgICAKKipnZ21hcCoqOiBodHRwczovL2dpdGh1Yi5jb20vZGthaGxlL2dnbWFwICAgIAoqKmxlYWZsZXQqKjogaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0LyAgICAKKipPcmVnb24qKiAodHV0b3JpYWwpOiBodHRwOi8vZ2VvZy51b3JlZ29uLmVkdS9iYXJ0bGVpbi9jb3Vyc2VzL2dlb2c0OTUvbGVjMDYuaHRtbCAgICAKCgpgYGB7cn0KCmBgYAoK